#ifndef BUF_MGR_HH
#define BUF_MGR_HH

#include <list>

class buffer_manager {
	int total_num_blocks;
	void *membank;
	list<block *> free_pool;
	list<block *> active_pool;
	double buffer_hit;
	double buffer_req;

public:
	buffer_manager(int num_blocks);

	int get_new_block(char *tname, int blocknum, block **p1);
	int get_block_num(char *tname, int blocknum, block **p1);
	int get_free_block(block **p1);

	double get_hit_rate();

	int print();
	int print_stats();
	int print_active_pool();

	int commit();
	~buffer_manager();
	
};

buffer_manager :: buffer_manager(int num_blocks)
{
	int num=0, i=0;
	block *temp=NULL;
	num = num_blocks;

	while(temp == NULL && num!=0)
	{
		temp = (block *) malloc(num * sizeof(class block));	
		if (temp == NULL)
		{
			num=num/2;
			if (num == 0)
			{
				FATAL("FATAL ERROR: No more memory space available\n");
			}

			FATAL("Lack of Memory: Trying with total_num_blocks=%d\n",num);
		}
	}			

	DBGPRINTF("Num of blocks allocated %d\n", num);
	DBGPRINTF("Size of each block is %d\n", sizeof(class block));
	total_num_blocks = num;
	membank 	= temp;
	buffer_hit = 0;
	buffer_req = 0;

	for (i=0; i<num; i++)
	{
		DBGPRINTF("Block #%d >>> %p\n",i, temp+i);
		free_pool.push_front(temp+i);
	}

	DBGPRINTF("membank >>> %p\n", membank);
}

double buffer_manager :: get_hit_rate()
{
	if(0 == buffer_req)
	{
		DBGPRINTF("HIT Rate(0/0): 0\n");
		return(0);
	}
	else
	{
		DBGPRINTF("HIT Rate (%.0lf/%.0lf) : %.2lf\n", buffer_hit, buffer_req, buffer_hit/buffer_req);
		return(buffer_hit/buffer_req);
	}
}

/*! Finds an LRU block and flushes it, if free pool is not empty */
int buffer_manager :: get_free_block(block **p1)
{
	block* node;

	/*! If free pool is not empty */
	if(!free_pool.empty()) 
	{
		/*! - return a free block from free pool */
		node = free_pool.back();
		free_pool.pop_back();
		active_pool.push_front(node);
		*p1 = node;
	}
	else /*! Else */
	{
		/*! - flush out an active, !pinned() block and return it */
		node = active_pool.back();
		active_pool.pop_back();
		while(node->pinned())
		{
			DBGPRINT("Block pinned to memory\n");
			active_pool.push_front(node);
			node = active_pool.back();
			active_pool.pop_back();
		}
		node->free();
		active_pool.push_front(node);
		*p1 = node;
	}
	
	return(0);
}

/*! Return a new block irrespective of blockno. or tablename */
int buffer_manager :: get_new_block (char *tname, int blocknum, block **p1)
{
	if(blocknum < 0)
	{
		FATAL("Invalid Block number %d\n", blocknum);
		exit(1);
	}

	/*! HIT, new block creation is always a HIT */
	DBGPRINTS(tname);
	DBGPRINTN(blocknum);
	buffer_hit++;
	buffer_req++;
	get_free_block(p1);
	return(0);

}

/*!
 *  Returns pointer to a free block p1
 *    - If block is already present in the active pool returns a pointer to it
 *    - Else calls get_free_block() to get free up a block if necessary 
 */
int buffer_manager :: get_block_num (char *tname, int blocknum, block **p1)
{
	block *temp;

	if(blocknum < 0)
	{
		FATAL("Invalid Block number %d\n", blocknum);
		exit(1);
	}

	buffer_req++;
	DBGPRINTS(tname);
	DBGPRINTN(blocknum);
	for(list<block *>::iterator i=active_pool.begin(); i != active_pool.end(); i++)
	{
		temp = *i;
		if(temp->get_block_no() == blocknum)
		{
			if(strcmp(temp->get_file_name(), tname) == 0)
			{
				DBGPRINTF("Block found !!!\n");
				/*! remove from the list and put it on front */
				active_pool.remove(temp);
				active_pool.push_front(temp);
				*p1 = temp;
				/*! HIT, found the block in the list */
				buffer_hit++;
				return(0);				
			}
		}
	}


	/*** U should get that not a new one, this is done by block class in initialization ***/
	get_free_block(&temp);

	*p1 = temp;
	return(0);
}


int buffer_manager :: print()
{
	int i=0;
	block *temp= (block *)membank;
	
	PRINT("\n%-8s\t%-8s\t%-8s","Buffer","File","Block");
	PRINT("\n%-8s\t%-8s\t%-8s","------","----","-----");
	for (i=0; i<total_num_blocks; i++)
	{
		PRINT("\n#%-8d", i+1);
		(temp+i)->print();
	}

	return(0);
}

int buffer_manager :: print_stats()
{
	HEAD("BUFFER Manager:");
	HEAD("---------------");
	SHEAD("Number of Blocks: %d", total_num_blocks);
	SHEAD("Number of Hits:  %.01lf", buffer_hit);
	SHEAD("Number of Reqs:  %.01lf", buffer_req);
	SHEAD("HIT Rate (%.0lf/%.0lf): %.2lf", buffer_hit, buffer_req, get_hit_rate());
	return(0);
}

int buffer_manager :: print_active_pool()
{
	block *temp;
	int j=0;

	for(list<block *>::iterator i=active_pool.begin(); i != active_pool.end(); i++)
	{
		SHEAD("\tBuffer #%d:\n", ++j);
		temp = *i;
		temp->print();
	}

	return(0);
}

int buffer_manager :: commit()
{
	block *temp;

	DBGENTER;
	for(list<block *>::iterator i=active_pool.begin(); i != active_pool.end(); i++)
	{
		temp = *i;
		temp->flush();
	}

	return(0);
}

/*!
 * - Free all the malloc'ed data
 * - Delete all the new'ed objects
 */
buffer_manager :: ~buffer_manager()
{
	commit();
	free(membank);
	membank = NULL;
}

#endif
